﻿using System;
using System.Collections.Generic;
using System.Linq;
using DotNetExtensions.Core.IOC;
using Autofac;
using ContainerBuilder = Autofac.ContainerBuilder;
using System.Reflection;
using System.IO;

namespace DotNetExtensions.Core.Autofac
{
    public static class AutofacBuilderExtensions
    {
        public static void RegistTypes(
             this ContainerBuilder builder, Action<BuilderOption> option)
        {

            RegistTypeResolver.Default.Builder = builder;

            BuilderOption builderOptions = new BuilderOption();
            option?.Invoke(builderOptions);

            builderOptions.Pattern.Add("DotNetExtensions*.dll");
            // load assemblies from pattern
            if (builderOptions.Pattern.Any())
            {
                string execAssemblyFileName = Assembly.GetExecutingAssembly().Location;
                string path = new FileInfo(execAssemblyFileName).DirectoryName;
                if (Directory.Exists(path))
                {
                    List<string> assemblyFiles = new List<string>();
                    foreach (var pattern in builderOptions.Pattern)
                    {
                        string[] files = Directory.GetFiles(path, pattern);
                        if (files != null && files.Length > 0) assemblyFiles.AddRange(files);
                    }
                    if (assemblyFiles.Any())
                    {
                        builderOptions.Assemblies.AddRange(assemblyFiles.Distinct().Select(file => Assembly.LoadFrom(file)));
                    }
                }
            }

            List<Type> types = builderOptions.Assemblies.SelectMany(a => a.GetTypes()).Distinct().ToList();
            // interface | if inherit IAutoInject or use AutoInjectAttribute
            List<Type> autoInjectFromInheritTypes = types.Where(t => t.IsInterface && t.GetInterfaces().Contains(typeof(IAutoInject))).Distinct().ToList();
            List<Type> autoInjectFormAttrTypes = types.Where(t => t.IsInterface && t.GetCustomAttribute<AutoInjectAttribute>() != null).Distinct().ToList();

            if (autoInjectFromInheritTypes.Any() || autoInjectFormAttrTypes.Any())
            {
                List<Type> autoInjectTypes = autoInjectFromInheritTypes.Union(autoInjectFormAttrTypes).Distinct().ToList();
                builderOptions.Filters.AddRange(autoInjectTypes.Select(type => new InjectTypeFilter { Kind = type.GetCustomAttribute<AutoInjectAttribute>()?.Kind ?? InjectKind.Typed, Type = type }));
            }

            // class | use AutoInjectAttribute
            List<Type> autoInjectClassTypes = types.Where(t => t.IsClass && t.GetCustomAttribute<AutoInjectAttribute>() != null).Distinct().ToList();
            if (autoInjectClassTypes.Any())
            {
                builderOptions.Filters.AddRange(autoInjectClassTypes.Select(type => new InjectTypeFilter { Kind = type.GetCustomAttribute<AutoInjectAttribute>().Kind, Type = type }));
            }

            foreach (var item in builderOptions.Filters)
            {
                if (item.Type == typeof(IAutoInject)) continue;
                if (item.Type == typeof(IDisposable)) continue;
                if (item.Type == typeof(object)) continue;

                List<Type> subs = null;
                if (item.Type.IsClass)
                {
                    // if class | use self
                    subs = new List<Type> { item.Type };
                }
                else if (item.Type.IsInterface)
                {
                    if (item.Type.IsGenericType)
                    {
                        subs = types.Where(t => t.IsClass && !t.IsAbstract && t.GetInterfaces().Any(x => x.Name == item.Type.Name)).ToList();
                    }
                    else
                    {
                        subs = types.Where(t => t.IsClass && t.GetInterfaces().Contains(item.Type)).ToList();
                    }
                }

                RegistTypeResolver.Default.RegistTypes.TryAdd(item.Type, subs);

                if (item.Kind == InjectKind.Markd) continue;

                foreach (var type in subs)
                {
                    var @inject = type.GetCustomAttribute<InjectAttribute>();
                    if (@inject == null) @inject = new InjectAttribute();

                    if (string.IsNullOrEmpty(@inject.Named)) @inject.Named = type.FullName;

                    @inject.Named = item.Type.Name + "-" + @inject.Named;

                    if (!type.IsGenericType && !type.ContainsGenericParameters && !type.GetGenericArguments().Any())
                    {
                        if (item.Kind == InjectKind.Named && @inject.Scope == InjectScope.Instance)
                        {
                            RegistInstance(builder, @inject.Named, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Named && @inject.Scope == InjectScope.Scoped)
                        {
                            RegistScoped(builder, @inject.Named, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Named && @inject.Scope == InjectScope.Single)
                        {
                            RegistSingle(builder, @inject.Named, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Typed && @inject.Scope == InjectScope.Instance)
                        {
                            RegistInstance(builder, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Typed && @inject.Scope == InjectScope.Scoped)
                        {
                            RegistScoped(builder, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Typed && @inject.Scope == InjectScope.Single)
                        {
                            RegistSingle(builder, type, item.Type);
                        }
                    }
                    else
                    {
                        // 增加泛型支持
                        if (item.Kind == InjectKind.Named && @inject.Scope == InjectScope.Instance)
                        {
                            RegistInstanceGeneric(builder, @inject.Named, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Named && @inject.Scope == InjectScope.Scoped)
                        {
                            RegistScopedGeneric(builder, @inject.Named, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Named && @inject.Scope == InjectScope.Single)
                        {
                            RegistSingleGeneric(builder, @inject.Named, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Typed && @inject.Scope == InjectScope.Instance)
                        {
                            RegistInstanceGeneric(builder, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Typed && @inject.Scope == InjectScope.Scoped)
                        {
                            RegistScopedGeneric(builder, type, item.Type);
                        }
                        else if (item.Kind == InjectKind.Typed && @inject.Scope == InjectScope.Single)
                        {
                            RegistSingleGeneric(builder, type, item.Type);
                        }
                    }
                }

            }
        }

        public static void RegistSingle(this ContainerBuilder builder, Type implType, Type type)
        {
            if (type.IsGenericType)
            {
                builder.RegisterType(implType)
                       .AsImplementedInterfaces()
                       .SingleInstance()
                       .PropertiesAutowired();
            }
            else
            {
                builder.RegisterType(implType)
                    .As(type)
                    .AsImplementedInterfaces()
                    .SingleInstance()
                    .PropertiesAutowired();
            }
        }
        public static void RegistSingleGeneric(this ContainerBuilder builder, Type implType, Type type)
        {
            builder.RegisterGeneric(implType)
               .As(type)
               .AsImplementedInterfaces()
               .SingleInstance()
               .PropertiesAutowired();
        }

        public static void RegistInstance(this ContainerBuilder builder, Type implType, Type type)
        {
            if (type.IsGenericType)
            {
                builder.RegisterType(implType)
                      .AsImplementedInterfaces()
                      .InstancePerDependency()
                      .PropertiesAutowired();
            }
            else
            {
                builder.RegisterType(implType)
                      .As(type)
                      .AsImplementedInterfaces()
                      .InstancePerDependency()
                      .PropertiesAutowired();
            }
        }
        public static void RegistInstanceGeneric(this ContainerBuilder builder, Type implType, Type type)
        {
            builder.RegisterGeneric(implType)
                       .As(type)
                       .AsImplementedInterfaces()
                       .InstancePerDependency()
                       .PropertiesAutowired();
        }

        public static void RegistScoped(this ContainerBuilder builder, Type implType, Type type)
        {
            if (type.IsGenericType)
            {
                builder.RegisterType(implType)
                       .AsImplementedInterfaces()
                       .InstancePerLifetimeScope()
                       .PropertiesAutowired();
            }
            else
            {
                builder.RegisterType(implType)
                      .As(type)
                      .AsImplementedInterfaces()
                      .InstancePerLifetimeScope()
                      .PropertiesAutowired();
            }
        }
        public static void RegistScopedGeneric(this ContainerBuilder builder, Type implType, Type type)
        {
            builder.RegisterGeneric(implType)
                       .As(type)
                       .AsImplementedInterfaces()
                       .InstancePerLifetimeScope()
                       .PropertiesAutowired();
        }

        public static void RegistSingle(this ContainerBuilder builder, string named, Type implType, Type type)
        {
            builder.RegisterType(implType)
               .Named(named, type)
               .AsImplementedInterfaces()
               .SingleInstance()
               .PropertiesAutowired();
        }
        public static void RegistSingleGeneric(this ContainerBuilder builder, string named, Type implType, Type type)
        {
            builder.RegisterGeneric(implType)
                    .Named(named, type)
                    .AsImplementedInterfaces()
                    .SingleInstance()
                    .PropertiesAutowired();
        }
        public static void RegistInstance(this ContainerBuilder builder, string named, Type implType, Type type)
        {
            builder.RegisterType(implType)
                             .Named(named, type)
                             .AsImplementedInterfaces()
                             .InstancePerDependency()
                             .PropertiesAutowired();

        }
        public static void RegistInstanceGeneric(this ContainerBuilder builder, string named, Type implType, Type type)
        {
            builder.RegisterGeneric(implType)
                             .Named(named, type)
                              .AsImplementedInterfaces()
                             .InstancePerDependency()
                             .PropertiesAutowired();

        }

        public static void RegistScoped(this ContainerBuilder builder, string named, Type implType, Type type)
        {
            builder.RegisterType(implType)
                           .Named(named, type)
                            .AsImplementedInterfaces()
                           .InstancePerLifetimeScope()
                           .PropertiesAutowired();
        }

        public static void RegistScopedGeneric(this ContainerBuilder builder, string named, Type implType, Type type)
        {
            builder.RegisterGeneric(implType)
                           .Named(named, type)
                           .AsImplementedInterfaces()
                           .InstancePerLifetimeScope()
                           .PropertiesAutowired();

        }
    }
}
